/* MIT License
 *
 * Copyright (c) 1998 Massachusetts Institute of Technology
 * Copyright (c) 2007 Daniel Stenberg
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * SPDX-License-Identifier: MIT
 */

#include "ares_private.h"

#ifdef HAVE_SYS_PARAM_H
#  include <sys/param.h>
#endif

#ifdef HAVE_NETINET_IN_H
#  include <netinet/in.h>
#endif

#ifdef HAVE_NETDB_H
#  include <netdb.h>
#endif

#ifdef HAVE_ARPA_INET_H
#  include <arpa/inet.h>
#endif

#if defined(ANDROID) || defined(__ANDROID__)
#  include <sys/system_properties.h>
#  include "ares_android.h"
/* From the Bionic sources */
#  define DNS_PROP_NAME_PREFIX "net.dns"
#  define MAX_DNS_PROPERTIES   8
#endif

#if defined(CARES_USE_LIBRESOLV)
#  include <resolv.h>
#endif

#if defined(USE_WINSOCK) && defined(HAVE_IPHLPAPI_H)
#  include <iphlpapi.h>
#endif

#include "ares_inet_net_pton.h"

static unsigned char ip_natural_mask(const struct ares_addr *addr)
{
  const unsigned char *ptr = NULL;
  /* This is an odd one.  If a raw ipv4 address is specified, then we take
   * what is called a natural mask, which means we look at the first octet
   * of the ip address and for values 0-127 we assume it is a class A (/8),
   * for values 128-191 we assume it is a class B (/16), and for 192-223
   * we assume it is a class C (/24).  223-239 is Class D which and 240-255 is
   * Class E, however, there is no pre-defined mask for this, so we'll use
   * /24 as well as that's what the old code did.
   *
   * For IPv6, we'll use /64.
   */

  if (addr->family == AF_INET6) {
    return 64;
  }

  ptr = (const unsigned char *)&addr->addr.addr4;
  if (*ptr < 128) {
    return 8;
  }

  if (*ptr < 192) {
    return 16;
  }

  return 24;
}

static ares_bool_t sortlist_append(struct apattern **sortlist, size_t *nsort,
                                   const struct apattern *pat)
{
  struct apattern *newsort;

  newsort = ares_realloc(*sortlist, (*nsort + 1) * sizeof(*newsort));
  if (newsort == NULL) {
    return ARES_FALSE; /* LCOV_EXCL_LINE: OutOfMemory */
  }

  *sortlist = newsort;

  memcpy(&(*sortlist)[*nsort], pat, sizeof(**sortlist));
  (*nsort)++;

  return ARES_TRUE;
}

static ares_status_t parse_sort(ares_buf_t *buf, struct apattern *pat)
{
  ares_status_t       status;
  const unsigned char ip_charset[]             = "ABCDEFabcdef0123456789.:";
  char                ipaddr[INET6_ADDRSTRLEN] = "";
  size_t              addrlen;

  memset(pat, 0, sizeof(*pat));

  /* Consume any leading whitespace */
  ares_buf_consume_whitespace(buf, ARES_TRUE);

  /* If no length, just ignore, return ENOTFOUND as an indicator */
  if (ares_buf_len(buf) == 0) {
    return ARES_ENOTFOUND;
  }

  ares_buf_tag(buf);

  /* Consume ip address */
  if (ares_buf_consume_charset(buf, ip_charset, sizeof(ip_charset) - 1) == 0) {
    return ARES_EBADSTR;
  }

  /* Fetch ip address */
  status = ares_buf_tag_fetch_string(buf, ipaddr, sizeof(ipaddr));
  if (status != ARES_SUCCESS) {
    return status;
  }

  /* Parse it to make sure its valid */
  pat->addr.family = AF_UNSPEC;
  if (ares_dns_pton(ipaddr, &pat->addr, &addrlen) == NULL) {
    return ARES_EBADSTR;
  }

  /* See if there is a subnet mask */
  if (ares_buf_begins_with(buf, (const unsigned char *)"/", 1)) {
    char                maskstr[16];
    const unsigned char ipv4_charset[] = "0123456789.";


    /* Consume / */
    ares_buf_consume(buf, 1);

    ares_buf_tag(buf);

    /* Consume mask */
    if (ares_buf_consume_charset(buf, ipv4_charset, sizeof(ipv4_charset) - 1) ==
        0) {
      return ARES_EBADSTR;
    }

    /* Fetch mask */
    status = ares_buf_tag_fetch_string(buf, maskstr, sizeof(maskstr));
    if (status != ARES_SUCCESS) {
      return status;
    }

    if (ares_str_isnum(maskstr)) {
      /* Numeric mask */
      int mask = atoi(maskstr);
      if (mask < 0 || mask > 128) {
        return ARES_EBADSTR;
      }
      if (pat->addr.family == AF_INET && mask > 32) {
        return ARES_EBADSTR;
      }
      pat->mask = (unsigned char)mask;
    } else {
      /* Ipv4 subnet style mask */
      struct ares_addr     maskaddr;
      const unsigned char *ptr;

      memset(&maskaddr, 0, sizeof(maskaddr));
      maskaddr.family = AF_INET;
      if (ares_dns_pton(maskstr, &maskaddr, &addrlen) == NULL) {
        return ARES_EBADSTR;
      }
      ptr       = (const unsigned char *)&maskaddr.addr.addr4;
      pat->mask = (unsigned char)(ares_count_bits_u8(ptr[0]) +
                                  ares_count_bits_u8(ptr[1]) +
                                  ares_count_bits_u8(ptr[2]) +
                                  ares_count_bits_u8(ptr[3]));
    }
  } else {
    pat->mask = ip_natural_mask(&pat->addr);
  }

  /* Consume any trailing whitespace */
  ares_buf_consume_whitespace(buf, ARES_TRUE);

  /* If we have any trailing bytes other than whitespace, its a parse failure */
  if (ares_buf_len(buf) != 0) {
    return ARES_EBADSTR;
  }

  return ARES_SUCCESS;
}

ares_status_t ares_parse_sortlist(struct apattern **sortlist, size_t *nsort,
                                  const char *str)
{
  ares_buf_t   *buf    = NULL;
  ares_status_t status = ARES_SUCCESS;
  ares_array_t *arr    = NULL;
  size_t        num    = 0;
  size_t        i;

  if (sortlist == NULL || nsort == NULL || str == NULL) {
    return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
  }

  if (*sortlist != NULL) {
    ares_free(*sortlist);
  }

  *sortlist = NULL;
  *nsort    = 0;

  buf = ares_buf_create_const((const unsigned char *)str, ares_strlen(str));
  if (buf == NULL) {
    status = ARES_ENOMEM;
    goto done;
  }

  /* Split on space or semicolon. It's not clear why the split on
     semicolon is supported, but the user of this function is
     ares_set_sortlist(), whose documentation does not mention
     semicolons, only spaces as valid separation characters. It is
     therefore unlikely that anyone uses semicolons.

     The old parser behaved this way and this behavior of splitting in
     semicolons was preserved in the parser rewrite on
     e72ae094855a0aed44afadfb4de462065e144185 (see also #653).*/
  status = ares_buf_split(buf, (const unsigned char *)" ;", 2,
                          ARES_BUF_SPLIT_NONE, 0, &arr);
  if (status != ARES_SUCCESS) {
    goto done;
  }

  num = ares_array_len(arr);
  for (i = 0; i < num; i++) {
    ares_buf_t    **bufptr = ares_array_at(arr, i);
    ares_buf_t     *entry  = *bufptr;

    struct apattern pat;

    status = parse_sort(entry, &pat);
    if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
      goto done;
    }

    if (status != ARES_SUCCESS) {
      continue;
    }

    if (!sortlist_append(sortlist, nsort, &pat)) {
      status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
      goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
    }
  }

  status = ARES_SUCCESS;

done:
  ares_buf_destroy(buf);
  ares_array_destroy(arr);

  if (status != ARES_SUCCESS) {
    ares_free(*sortlist);
    *sortlist = NULL;
    *nsort    = 0;
  }

  return status;
}

static ares_status_t config_search(ares_sysconfig_t *sysconfig, const char *str,
                                   size_t max_domains)
{
  if (sysconfig->domains && sysconfig->ndomains > 0) {
    /* if we already have some domains present, free them first */
    ares_strsplit_free(sysconfig->domains, sysconfig->ndomains);
    sysconfig->domains  = NULL;
    sysconfig->ndomains = 0;
  }

  sysconfig->domains = ares_strsplit(str, ", ", &sysconfig->ndomains);
  if (sysconfig->domains == NULL) {
    return ARES_ENOMEM;
  }

  /* Truncate if necessary */
  if (max_domains && sysconfig->ndomains > max_domains) {
    size_t i;
    for (i = max_domains; i < sysconfig->ndomains; i++) {
      ares_free(sysconfig->domains[i]);
      sysconfig->domains[i] = NULL;
    }
    sysconfig->ndomains = max_domains;
  }

  return ARES_SUCCESS;
}

static ares_status_t buf_fetch_string(ares_buf_t *buf, char *str,
                                      size_t str_len)
{
  ares_status_t status;
  ares_buf_tag(buf);
  ares_buf_consume(buf, ares_buf_len(buf));

  status = ares_buf_tag_fetch_string(buf, str, str_len);
  return status;
}

static ares_status_t config_lookup(ares_sysconfig_t *sysconfig, ares_buf_t *buf,
                                   const char *separators)
{
  ares_status_t status;
  char          lookupstr[32];
  size_t        lookupstr_cnt = 0;
  char        **lookups       = NULL;
  size_t        num           = 0;
  size_t        i;
  size_t        separators_len = ares_strlen(separators);

  status =
    ares_buf_split_str(buf, (const unsigned char *)separators, separators_len,
                       ARES_BUF_SPLIT_TRIM, 0, &lookups, &num);
  if (status != ARES_SUCCESS) {
    goto done;
  }

  for (i = 0; i < num; i++) {
    const char *value = lookups[i];
    char        ch;

    if (ares_strcaseeq(value, "dns") || ares_strcaseeq(value, "bind") ||
        ares_strcaseeq(value, "resolv") || ares_strcaseeq(value, "resolve")) {
      ch = 'b';
    } else if (ares_strcaseeq(value, "files") ||
               ares_strcaseeq(value, "file") ||
               ares_strcaseeq(value, "local")) {
      ch = 'f';
    } else {
      continue;
    }

    /* Look for a duplicate and ignore */
    if (memchr(lookupstr, ch, lookupstr_cnt) == NULL) {
      lookupstr[lookupstr_cnt++] = ch;
    }
  }

  if (lookupstr_cnt) {
    lookupstr[lookupstr_cnt] = 0;
    ares_free(sysconfig->lookups);
    sysconfig->lookups = ares_strdup(lookupstr);
    if (sysconfig->lookups == NULL) {
      status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
      goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
    }
  }

  status = ARES_SUCCESS;

done:
  if (status != ARES_ENOMEM) {
    status = ARES_SUCCESS;
  }
  ares_free_array(lookups, num, ares_free);
  return status;
}

static ares_status_t process_option(ares_sysconfig_t *sysconfig,
                                    ares_buf_t       *option)
{
  char        **kv  = NULL;
  size_t        num = 0;
  const char   *key;
  const char   *val;
  unsigned int  valint = 0;
  ares_status_t status;

  /* Split on : */
  status = ares_buf_split_str(option, (const unsigned char *)":", 1,
                              ARES_BUF_SPLIT_TRIM, 2, &kv, &num);
  if (status != ARES_SUCCESS) {
    goto done;
  }

  if (num < 1) {
    status = ARES_EBADSTR;
    goto done;
  }

  key = kv[0];
  if (num == 2) {
    val    = kv[1];
    valint = (unsigned int)strtoul(val, NULL, 10);
  }

  if (ares_streq(key, "ndots")) {
    sysconfig->ndots = valint;
  } else if (ares_streq(key, "retrans") || ares_streq(key, "timeout")) {
    if (valint == 0) {
      return ARES_EFORMERR;
    }
    sysconfig->timeout_ms = valint * 1000;
  } else if (ares_streq(key, "retry") || ares_streq(key, "attempts")) {
    if (valint == 0) {
      return ARES_EFORMERR;
    }
    sysconfig->tries = valint;
  } else if (ares_streq(key, "rotate")) {
    sysconfig->rotate = ARES_TRUE;
  } else if (ares_streq(key, "use-vc") || ares_streq(key, "usevc")) {
    sysconfig->usevc = ARES_TRUE;
  }

done:
  ares_free_array(kv, num, ares_free);
  return status;
}

ares_status_t ares_sysconfig_set_options(ares_sysconfig_t *sysconfig,
                                         const char       *str)
{
  ares_buf_t   *buf     = NULL;
  ares_array_t *options = NULL;
  size_t        num;
  size_t        i;
  ares_status_t status;

  buf = ares_buf_create_const((const unsigned char *)str, ares_strlen(str));
  if (buf == NULL) {
    return ARES_ENOMEM;
  }

  status = ares_buf_split(buf, (const unsigned char *)" \t", 2,
                          ARES_BUF_SPLIT_TRIM, 0, &options);
  if (status != ARES_SUCCESS) {
    goto done;
  }

  num = ares_array_len(options);
  for (i = 0; i < num; i++) {
    ares_buf_t **bufptr = ares_array_at(options, i);
    ares_buf_t  *valbuf = *bufptr;

    status = process_option(sysconfig, valbuf);
    /* Out of memory is the only fatal condition */
    if (status == ARES_ENOMEM) {
      goto done; /* LCOV_EXCL_LINE: OutOfMemory */
    }
  }

  status = ARES_SUCCESS;

done:
  ares_array_destroy(options);
  ares_buf_destroy(buf);
  return status;
}

ares_status_t ares_init_by_environment(ares_sysconfig_t *sysconfig)
{
  const char   *localdomain;
  const char   *res_options;
  ares_status_t status;

  localdomain = getenv("LOCALDOMAIN");
  if (localdomain) {
    char *temp = ares_strdup(localdomain);
    if (temp == NULL) {
      return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
    }
    status = config_search(sysconfig, temp, 1);
    ares_free(temp);
    if (status != ARES_SUCCESS) {
      return status;
    }
  }

  res_options = getenv("RES_OPTIONS");
  if (res_options) {
    status = ares_sysconfig_set_options(sysconfig, res_options);
    if (status != ARES_SUCCESS) {
      return status;
    }
  }

  return ARES_SUCCESS;
}

/* Configuration Files:
 *  /etc/resolv.conf
 *    - All Unix-like systems
 *    - Comments start with ; or #
 *    - Lines have a keyword followed by a value that is interpreted specific
 *      to the keyword:
 *    - Keywords:
 *      - nameserver - IP address of nameserver with optional port (using a :
 *        prefix). If using an ipv6 address and specifying a port, the ipv6
 *        address must be encapsulated in brackets. For link-local ipv6
 *        addresses, the interface can also be specified with a % prefix. e.g.:
 *          "nameserver [fe80::1]:1234%iface"
 *        This keyword may be specified multiple times.
 *      - search - whitespace separated list of domains
 *      - domain - obsolete, same as search except only a single domain
 *      - lookup / hostresorder - local, bind, file, files
 *      - sortlist - whitespace separated ip-address/netmask pairs
 *      - options - options controlling resolver variables
 *        - ndots:n - set ndots option
 *        - timeout:n (retrans:n) - timeout per query attempt in seconds
 *        - attempts:n (retry:n) - number of times resolver will send query
 *        - rotate - round-robin selection of name servers
 *        - use-vc / usevc - force tcp
 *  /etc/nsswitch.conf
 *    - Modern Linux, FreeBSD, HP-UX, Solaris
 *    - Search order set via:
 *      "hosts: files dns mdns4_minimal mdns4"
 *      - files is /etc/hosts
 *      - dns is dns
 *      - mdns4_minimal does mdns only if ending in .local
 *      - mdns4 does not limit to domains ending in .local
 *  /etc/netsvc.conf
 *    - AIX
 *    - Search order set via:
 *      "hosts = local , bind"
 *      - bind is dns
 *      - local is /etc/hosts
 *  /etc/svc.conf
 *    - Tru64
 *    - Same format as /etc/netsvc.conf
 *  /etc/host.conf
 *    - Early FreeBSD, Early Linux
 *    - Not worth supporting, format varied based on system, FreeBSD used
 *      just a line per search order, Linux used "order " and a comma
 *      delimited list of "bind" and "hosts"
 */


/* This function will only return ARES_SUCCESS or ARES_ENOMEM.  Any other
 * conditions are ignored.  Users may mess up config files, but we want to
 * process anything we can. */
ares_status_t ares_sysconfig_parse_resolv_line(const ares_channel_t *channel,
                                               ares_sysconfig_t     *sysconfig,
                                               ares_buf_t           *line)
{
  char          option[32];
  char          value[512];
  ares_status_t status = ARES_SUCCESS;

  /* Ignore lines beginning with a comment */
  if (ares_buf_begins_with(line, (const unsigned char *)"#", 1) ||
      ares_buf_begins_with(line, (const unsigned char *)";", 1)) {
    return ARES_SUCCESS;
  }

  ares_buf_tag(line);

  /* Shouldn't be possible, but if it happens, ignore the line. */
  if (ares_buf_consume_nonwhitespace(line) == 0) {
    return ARES_SUCCESS;
  }

  status = ares_buf_tag_fetch_string(line, option, sizeof(option));
  if (status != ARES_SUCCESS) {
    return ARES_SUCCESS;
  }

  ares_buf_consume_whitespace(line, ARES_TRUE);

  status = buf_fetch_string(line, value, sizeof(value));
  if (status != ARES_SUCCESS) {
    return ARES_SUCCESS;
  }

  ares_str_trim(value);
  if (*value == 0) {
    return ARES_SUCCESS;
  }

  /* At this point we have a string option and a string value, both trimmed
   * of leading and trailing whitespace.  Lets try to evaluate them */
  if (ares_streq(option, "domain")) {
    /* Domain is legacy, don't overwrite an existing config set by search */
    if (sysconfig->domains == NULL) {
      status = config_search(sysconfig, value, 1);
    }
  } else if (ares_streq(option, "lookup") ||
             ares_streq(option, "hostresorder")) {
    ares_buf_tag_rollback(line);
    status = config_lookup(sysconfig, line, " \t");
  } else if (ares_streq(option, "search")) {
    status = config_search(sysconfig, value, 0);
  } else if (ares_streq(option, "nameserver")) {
    status = ares_sconfig_append_fromstr(channel, &sysconfig->sconfig, value,
                                         ARES_TRUE);
  } else if (ares_streq(option, "sortlist")) {
    /* Ignore all failures except ENOMEM.  If the sysadmin set a bad
     * sortlist, just ignore the sortlist, don't cause an inoperable
     * channel */
    status =
      ares_parse_sortlist(&sysconfig->sortlist, &sysconfig->nsortlist, value);
    if (status != ARES_ENOMEM) {
      status = ARES_SUCCESS;
    }
  } else if (ares_streq(option, "options")) {
    status = ares_sysconfig_set_options(sysconfig, value);
  }

  return status;
}

/* This function will only return ARES_SUCCESS or ARES_ENOMEM.  Any other
 * conditions are ignored.  Users may mess up config files, but we want to
 * process anything we can. */
static ares_status_t parse_nsswitch_line(const ares_channel_t *channel,
                                         ares_sysconfig_t     *sysconfig,
                                         ares_buf_t           *line)
{
  char          option[32];
  ares_status_t status = ARES_SUCCESS;
  ares_array_t *sects  = NULL;
  ares_buf_t  **bufptr;
  ares_buf_t   *buf;

  (void)channel;

  /* Ignore lines beginning with a comment */
  if (ares_buf_begins_with(line, (const unsigned char *)"#", 1)) {
    return ARES_SUCCESS;
  }

  /* database : values (space delimited) */
  status = ares_buf_split(line, (const unsigned char *)":", 1,
                          ARES_BUF_SPLIT_TRIM, 2, &sects);

  if (status != ARES_SUCCESS || ares_array_len(sects) != 2) {
    goto done;
  }

  bufptr = ares_array_at(sects, 0);
  buf    = *bufptr;

  status = buf_fetch_string(buf, option, sizeof(option));
  if (status != ARES_SUCCESS) {
    goto done;
  }

  /* Only support "hosts:" */
  if (!ares_streq(option, "hosts")) {
    goto done;
  }

  /* Values are space separated */
  bufptr = ares_array_at(sects, 1);
  buf    = *bufptr;
  status = config_lookup(sysconfig, buf, " \t");

done:
  ares_array_destroy(sects);
  if (status != ARES_ENOMEM) {
    status = ARES_SUCCESS;
  }
  return status;
}

/* This function will only return ARES_SUCCESS or ARES_ENOMEM.  Any other
 * conditions are ignored.  Users may mess up config files, but we want to
 * process anything we can. */
static ares_status_t parse_svcconf_line(const ares_channel_t *channel,
                                        ares_sysconfig_t     *sysconfig,
                                        ares_buf_t           *line)
{
  char          option[32];
  ares_buf_t  **bufptr;
  ares_buf_t   *buf;
  ares_status_t status = ARES_SUCCESS;
  ares_array_t *sects  = NULL;

  (void)channel;

  /* Ignore lines beginning with a comment */
  if (ares_buf_begins_with(line, (const unsigned char *)"#", 1)) {
    return ARES_SUCCESS;
  }

  /* database = values (comma delimited)*/
  status = ares_buf_split(line, (const unsigned char *)"=", 1,
                          ARES_BUF_SPLIT_TRIM, 2, &sects);

  if (status != ARES_SUCCESS || ares_array_len(sects) != 2) {
    goto done;
  }

  bufptr = ares_array_at(sects, 0);
  buf    = *bufptr;
  status = buf_fetch_string(buf, option, sizeof(option));
  if (status != ARES_SUCCESS) {
    goto done;
  }

  /* Only support "hosts=" */
  if (!ares_streq(option, "hosts")) {
    goto done;
  }

  /* Values are comma separated */
  bufptr = ares_array_at(sects, 1);
  buf    = *bufptr;
  status = config_lookup(sysconfig, buf, ",");

done:
  ares_array_destroy(sects);
  if (status != ARES_ENOMEM) {
    status = ARES_SUCCESS;
  }
  return status;
}


ares_status_t ares_sysconfig_process_buf(const ares_channel_t    *channel,
                                         ares_sysconfig_t        *sysconfig,
                                         ares_buf_t              *buf,
                                         ares_sysconfig_line_cb_t cb)
{
  ares_array_t *lines  = NULL;
  size_t        num;
  size_t        i;
  ares_status_t status;

  status = ares_buf_split(buf, (const unsigned char *)"\n", 1,
                          ARES_BUF_SPLIT_TRIM, 0, &lines);
  if (status != ARES_SUCCESS) {
    goto done;
  }

  num = ares_array_len(lines);
  for (i = 0; i < num; i++) {
    ares_buf_t **bufptr = ares_array_at(lines, i);
    ares_buf_t  *line   = *bufptr;

    status = cb(channel, sysconfig, line);
    if (status != ARES_SUCCESS) {
      goto done;
    }
  }

done:
  ares_array_destroy(lines);
  return status;
}

/* Should only return:
 *  ARES_ENOTFOUND - file not found
 *  ARES_EFILE     - error reading file (perms)
 *  ARES_ENOMEM    - out of memory
 *  ARES_SUCCESS   - file processed, doesn't necessarily mean it was a good
 *                   file, but we're not erroring out if we can't parse
 *                   something (or anything at all) */
static ares_status_t process_config_lines(const ares_channel_t    *channel,
                                          const char              *filename,
                                          ares_sysconfig_t        *sysconfig,
                                          ares_sysconfig_line_cb_t cb)
{
  ares_status_t status = ARES_SUCCESS;
  ares_buf_t   *buf    = NULL;

  buf = ares_buf_create();
  if (buf == NULL) {
    status = ARES_ENOMEM;
    goto done;
  }

  status = ares_buf_load_file(filename, buf);
  if (status != ARES_SUCCESS) {
    goto done;
  }

  status = ares_sysconfig_process_buf(channel, sysconfig, buf, cb);

done:
  ares_buf_destroy(buf);

  return status;
}

ares_status_t ares_init_sysconfig_files(const ares_channel_t *channel,
                                        ares_sysconfig_t     *sysconfig,
                                        ares_bool_t process_resolvconf)
{
  ares_status_t status = ARES_SUCCESS;

  /* Resolv.conf */
  if (process_resolvconf) {
    status = process_config_lines(channel,
                                  (channel->resolvconf_path != NULL)
                                    ? channel->resolvconf_path
                                    : PATH_RESOLV_CONF,
                                  sysconfig, ares_sysconfig_parse_resolv_line);
    if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
      goto done;
    }
  }

  /* Nsswitch.conf */
  status = process_config_lines(channel, "/etc/nsswitch.conf", sysconfig,
                                parse_nsswitch_line);
  if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
    goto done;
  }

  /* netsvc.conf */
  status = process_config_lines(channel, "/etc/netsvc.conf", sysconfig,
                                parse_svcconf_line);
  if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
    goto done;
  }

  /* svc.conf */
  status = process_config_lines(channel, "/etc/svc.conf", sysconfig,
                                parse_svcconf_line);
  if (status != ARES_SUCCESS && status != ARES_ENOTFOUND) {
    goto done;
  }

  status = ARES_SUCCESS;

done:
  return status;
}
